/* ==========================================================================
	Class :			CStdDiskObject

	Author :		Johan Rosengren, Abstrakt Mekanik AB

	Date :			2004-03-12

	Purpose :		"CStdDiskObject" encapsulates several high-level file- 
					and directory operations. 	

	Description :	A class for directory and file handling without using MFC. 
					Neither is the class using the Windows API and should 
					therefore, at least in theory, be portable. Note that as 
					it is UNICODE-aware, this assumes that there is a tchar.h 
					defining either normal or wide-char functions depending 
					on the _UNICODE-flag. "tstring" is defined as either 
					"std::wstring" or "std::string", also depending on the 
					flag. I've disabled the warnings normally giving problems 
					when including STL in older MSVC-projects as well.

					The class uses, as opposed to "CString" and "CStringArray", 
					"std::string" and "std::vector".

					All error handling is managed internally, and the 
					different API-functions return a "bool" to signal success 
					or failure. In case of failure, "false" returned, the 
					member function "GetErrorMessage" can be called to get a
					"tstring" with the error message.

					For localization reasons, I've included only the most 
					basic error reporting. The error reporting functions have 
					been made virtual, and I suggest that the class is derived 
					from and your own implementations are included - rather 
					than hacking the class itself. For example, 
					"SetSystemErrorMessage" could set a string depending on the 
					inparam error value - this value is taken from "errno".


	Usage :			The class is used by creating a "CStdDiskObject", and 
					calling the methods of the class. No other setup is 
					necessary. If a "HWND" is submitted to the "ctor", and 
					WIN32 is defined, "CStdDiskObject" will give feedback 
					by calling "SetWindowText" with, for example, filenames 
					during processing.This means that a static (or other 
					appropriate control) can be set up to display the file 
					currently copied, for example.

					In the project settings, set not using precompiled headers 
					for this file if project uses them.

					A big thanks to the Webdude, David Mackay, for starting me 
					off on this, as well as testing and other encouragements.

   ========================================================================*/

// File and directory handling
#include <stdio.h>
#include <direct.h>
#include <io.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>

#include "StdDiskObject.h"


bool copyfile( LPCTSTR from, LPCSTR to );

////////////////////////////////////////////////////////////////////
// CStdDiskObject construction/destruction/initialization

CStdDiskObject::CStdDiskObject()
/* ============================================================
	Function :		CStdDiskObject::CStdDiskObject
	Description :	Constructor
	Access :		Public

	Return :		void
	Parameters :	none
					
	Usage :			Should normally be created on the stack.

   ============================================================*/
{
	m_feedbackWindow = NULL;
}

#ifdef WIN32

CStdDiskObject::CStdDiskObject( HWND feedbackWindow )
/* ============================================================
	Function :		CStdDiskObject::CStdDiskObject
	Description :	Constructor
	Access :		Public
					
	Return :		void
	Parameters :	HWND hwndFeedback	-	"HWND" to feedback 
											window

 	Usage :			Should normally be created on the stack.

  ============================================================*/
{
	m_feedbackWindow = feedbackWindow;
}

#endif

CStdDiskObject::~CStdDiskObject( )
/* ============================================================
	Function :		CStdDiskObject::~CStdDiskObject
	Description :	Destructor
	Access :		Public
					
	Return :		void
	Parameters :	none

	Usage :			Should normally be created on the stack.

   ============================================================*/
{
}

////////////////////////////////////////////////////////////////////
// CStdDiskObject operations
//

// File operations

bool CStdDiskObject::CopyFiles( const tstring& sourceDirectory,const tstring& destDirectory )
/* ============================================================
	Function :		CStdDiskObject::CopyFiles
	Description :	The member copies all files from 
					"sourceDirectory" to "destDirectory". 
					Subdirectories will not be copied.
	Access :		Public
					
	Return :		bool					-	"true" if OK. 
												"GetErrorMessage" 
												will return 
												errors.
	Parameters :	tstring sourceDirectory	-	Copy from. If
												empty, current.
					tstring destDirectory	-	Copy to. If 
												empty, current.
					
	Usage :			Call to copy the files from one directory to 
					another.

  ============================================================*/
{
	bool result = false;
	ClearError( );
	tstring source( sourceDirectory );
	tstring dest( destDirectory );
	if( QualifyPath( source ) )
	{
		if( QualifyPath( dest ) )
		{

			// First, we enumerate all files
			vector< tstring > files;
			result = EnumFilesInDirectory( source, files );
			if( result )
			{
				// Create the destination directory, if necessary
				result = CreateDirectory( dest );
				if( result )
				{
					int max = files.size( );
					for( int t = 0 ; t < max ; t++ )
					{
						// Copy the files
						tstring file;
						file = files[ t ];
						TriggerStdDiskObject( file.c_str() );
						tstring sourcefile( source + file );
						tstring destfile( dest + file );
						result = copyfile( sourcefile.c_str(), destfile.c_str() );
						if( !result )
						{

							SetSystemErrorMessage( errno, 
													source + 
													file + 
													_T( " -> " ) + 
													dest + 
													file );
							t = max;
						}
					}
				}
			}
		}
	}

	return result;
}

bool CStdDiskObject::CopyFiles( vector< tstring >& files,const tstring& destDirectory )
/* ============================================================
	Function :		CStdDiskObject::CopyFiles
	Description :	The function copies the files in the 
					"vector< tstring >" "files" to the directory 
					"destDirectory". Existing files will be
					overwritten. The destination will be 
					created if it doesn't already exist.
	Access :		Public
					
	Return :		bool					-	"true" if OK. 
												"GetErrorMessage" 
												will return 
												errors.
	Parameters :	vector< tstring >& files		-	a tstring array 
												with the files 
												to copy
					tstring destDirectory	-	destination
					
	Usage :			Copy a list of files to a directory.

   ============================================================*/
{
	ClearError( );
	tstring dest( destDirectory );

	bool result = true;
	if( files.size( ) )
	{
		result = QualifyPath( dest );
		if( result )		
		{
			// Create destination, if necessary
			result = CreateDirectory( dest );
			if( result )
			{
				int max = files.size( );
				for( int t = 0 ; t < max ; t++ )
				{
					// Loop and copy the files
					tstring file;
					file = files[ t ];
					if( file.length( ) )
					{
						TriggerStdDiskObject( file.c_str() );
						QualifyFile( file );

						// Create destination filename
						tstring to = dest + GetFileName( file );
						result = copyfile( file.c_str(), to.c_str() );
						if( !result )
						{

							SetSystemErrorMessage( errno,
													file + 
													_T( " -> " ) + 
													dest + 
													file );
							t = max;
						}
					}
				}
			}
		}
	}
	else
	{
		SetInternalErrorMessage( );
		result =  false;
	}

	return result;
}

bool CStdDiskObject::CopyFile( const tstring& sourceFile,const tstring& destDirectory )
/* ============================================================
	Function :		CStdDiskObject::CopyFile
	Description :	Will copy "sourceFile" to "destDirectory". 
					An existing file will be overwritten. The 
					directory will be created if it doesn't exist.
	Access :		Public
					
	Return :		bool					-	"true" if OK. 
												"GetErrorMessage" 
												will contain 
												errors
	Parameters :	tstring sourceFile		-	file to copy
					tstring destDirectory	-	destination
					
	Usage :			Call to copy a file to a directory.

  ============================================================*/
{
	ClearError( );
	tstring source( sourceFile );
	tstring dest( destDirectory );

	bool result = false;
	if( sourceFile.length( ) )
	{
		if( QualifyFile( source ) )
		{
			if( QualifyPath( dest ) )
			{
				if( FileExists( source ) )
				{
					// Creating destDirectory if necessary.
					if( CreateDirectory( dest ) )
					{
						tstring filePart = GetFileName( source );
						// Copy the file
						TriggerStdDiskObject( filePart.c_str() );
						if( copyfile( source.c_str(), ( dest + filePart ).c_str() ) )
							result = true;
						else
							SetSystemErrorMessage( errno, source );
					}
				}
			}
		}
	}
	else
		SetInternalErrorMessage( );

	return result;
}

bool CStdDiskObject::CopyFile( const tstring& sourceFile,const tstring& destDirectory, const tstring& destFile )
/* ============================================================
	Function :		CStdDiskObject::CopyFile
	Description :	Will copy "sourceFile" to "destDirectory" 
					with the new name "destFile". 
					An existing file will be overwritten. The 
					directory will be created if it doesn't exist.
	Access :		Public
					
	Return :		bool					-	"true" if OK. 
												"GetErrorMessage" 
												will contain 
												errors
	Parameters :	tstring sourceFile		-	file to copy
					tstring destDirectory	-	destination
					tstring destFile		-	destination file name 
												(in "destDirectory")
					
	Usage :			Call to copy a file to a directory.

  ============================================================*/
{
	ClearError( );
	tstring source( sourceFile );
	tstring destDir( destDirectory );
	tstring dest( destFile );

	bool result = false;
	if( sourceFile.length( ) )
	{
		if( QualifyFile( source ) )
		{
			if( QualifyPath( destDir ) )
			{
				dest = destDir + dest;
				if( FileExists( source ) )
				{
					// Creating destDirectory if necessary.
					if( CreateDirectory( destDir ) )
					{
						// Copy the file
						TriggerStdDiskObject( dest.c_str() );
						if( copyfile( source.c_str(), dest.c_str() ) )
							result = true;
						else
							SetSystemErrorMessage( errno, source );
					}
				}
			}
		}
	}
	else
		SetInternalErrorMessage();

	return result;
}

bool CStdDiskObject::RemoveFile( const tstring& sourceFile )
/* ============================================================
	Function :		CStdDiskObject::RemoveFile
	Description :	Will remove "sourceFile".
	Access :		Public
					
	Return :		bool						-	"true" if 
													removed ok
	Parameters :	const tstring& sourceFile	-	File to
													remove
					
	Usage :			Call to delete a file. Added for reasons 
					of symmetry.

   ============================================================*/
{
	ClearError( );
	tstring source( sourceFile );

	bool result = false;
	if( sourceFile.length() )
	{
		result = QualifyFile( source );
		if( result )
		{

			int remove = _tunlink( source.c_str() );
			if( remove == _IO_FAILURE )
			{
				SetSystemErrorMessage( errno, source );
				result = false;
			}
		}
	}
	else
		SetInternalErrorMessage();

	return result;
}

////////////////////////////////////////
// Directory operations
//

bool CStdDiskObject::DirectoryExists( const tstring& directory )
/* ============================================================
	Function :		CStdDiskObject::DirectoryExists
	Description :	Returns "true" if the directory "directory"
					exists
	Access :		Public
					
	Return :		bool				-	"true" if found
	Parameters :	tstring directory	-	directory to check

	Usage :			Call to check for directory existence.

  ============================================================*/
{
	ClearError( );

	bool result = false;

	if( directory.length( ) )
	{
		tstring indir( directory );
		if( QualifyPath( indir ) )
		{
			TCHAR buff[ _MAX_PATH ];
			_tgetcwd( buff, _MAX_PATH );

			if( _tchdir( indir.c_str() ) != _IO_FAILURE )
				result = true;

			_tchdir( buff );
		}
	}
	else
	{
		SetInternalErrorMessage( );
	}

	return result;
}

bool CStdDiskObject::CreateDirectory( const tstring& directory )
/* ============================================================
	Function :		CStdDiskObject::CreateDirectory
	Description :	Will recursively create the directory 
					"directory".
	Access :		Public

	Return :		bool				-	"true" if OK. 
											"GetErrorMessage" 
											will get an 
											error tstring if 
											"false"
	Parameters :	tstring directory	-	directory to 
											create

	Usage :			Call to create a directory chain.

  ============================================================*/
{
	ClearError( );

	bool result = true;
	tstring indir( directory );
	if( indir.length( ) )
	{
		result = QualifyPath( indir );
		if( result )
		{

			_TCHAR drive[ _MAX_PATH ];
			_TCHAR dir[ _MAX_DIR ];
			_TCHAR fname[ _MAX_FNAME ];
			_TCHAR ext[ _MAX_EXT ];

			// Split directory into parts
			_tsplitpath( indir.c_str(), drive, dir, fname, ext );

			TCHAR currentDirectory[ _MAX_PATH ];
			_tgetcwd( currentDirectory, _MAX_PATH );

			vector< tstring > directories;
			tstring parts = dir;

			if( parts.length( ) > 2 )
			{
				if( parts.substr( 0, 2 ) == _T( "\\\\" ) )
				{
					// We have an UNC name
					tstring strComputer;
					parts = parts.substr( 2, parts.length( ) - 2 );
					basic_string <char>::size_type findDir = parts.find( _TCHAR( '\\' ) );
					if( findDir != tstring::npos )
					{
						strComputer = _T( "\\\\" ) + parts.substr( 0, findDir );
						parts = parts.substr( findDir, parts.length( ) - ( findDir + 1 ) );
					}
					_tcscpy( drive, strComputer.c_str() );
				}
			}

			tstring strRoot( drive );

			// Strip leading \'s
			while( parts.length( ) && parts[0] == _TCHAR( '\\' ) )
				parts = parts.substr( 1, parts.length( ) - 1 );

			// Cut into separate directories
			basic_string <char>::size_type find = parts.find( _TCHAR( '\\' ) );
			while( find != tstring::npos )
			{
				directories.push_back( parts.substr( 0, find ) );
				parts = parts.substr( find + 1, parts.length( ) - ( find + 1 ) );
				find = parts.find( _TCHAR( '\\' ) );
			}

			if( parts.length( ) )
				directories.push_back( parts.c_str() );

			if( _tcslen( fname ) )
				directories.push_back( fname );

			// Loop directories one-by-one, creating as necessary
			int max = directories.size( );
			tstring strCurrentDirectory( strRoot );

			for( int t = 0 ; t < max ; t++ )
			{

				_tchdir( strCurrentDirectory.c_str() );
				strCurrentDirectory += _TCHAR( '\\' ) + directories[ t ];
				TriggerStdDiskObject( strCurrentDirectory.c_str() );
				_tmkdir( strCurrentDirectory.c_str() );

			}

			int change = _tchdir( strCurrentDirectory.c_str() );
			if( change != _IO_FAILURE )
				result = true;

			_tchdir( currentDirectory );
		}
	}
	else
	{
		SetInternalErrorMessage( );
		result = false;
	}
	return result;
}

bool CStdDiskObject::EmptyDirectory( const tstring& directory )
/* ============================================================
	Function :		CStdDiskObject::EmptyDirectory
	Description :	Will delete all files in directory. 
	Access :		Public

	Return :		bool				-	"true" if OK. 
											"GetErrorMessage" will 
											get an error 
											tstring if "false"
	Parameters :	tstring directory	-	the directory to
											empty.

	Usage :			Call to empty a directory.

  ============================================================*/
{
	bool result = false;
	ClearError( );
	tstring indir( directory );
	if( QualifyPath( indir ) )
	{

		// Enumerate all files
		vector< tstring > files;
		result = EnumFilesInDirectory( indir, files );
		if( result )
		{
			int max = files.size( );
			for( int t = 0 ; t < max ; t++ )
			{
				// Loop and delete
				tstring file = files[ t ];
				TriggerStdDiskObject( file.c_str() );
				int remove = _tunlink( ( indir + file ).c_str() );
				if( remove != 0 )
				{
					SetSystemErrorMessage( errno, indir + file );
					t = max;
					result = false;
				}

			}
		}
	}

	return result;
}

bool CStdDiskObject::RemoveDirectory( const tstring& directory )
/* ============================================================
	Function :		CStdDiskObject::RemoveDirectory
	Description :	Will remove the directory "directory", even
					if not empty. Will not remove 
					subdirectories.
	Access :		Public
					
	Return :		bool				-	"true" if OK. 
											"GetErrorMessage"
											will get an error 
											tstring if "false"
	Parameters :	tstring directory	-	directory to 
											remove.

	Usage :			Call to remove a directory.

  ============================================================*/
{
	ClearError( );

	bool result = true;
	tstring indir( directory );
	if( indir.length( ) )
	{
		result = QualifyPath( indir );
		if( result )
		{

			// Wipe and remove directory
			result = EmptyDirectory( indir );
			if( result )
			{
				TriggerStdDiskObject( indir.c_str() );
				int create = _trmdir( indir.c_str() );
				if( create != 0 )
					SetSystemErrorMessage( errno, indir );
				else
					result = true;
			}
		}
	}
	else
	{
		// Small sanity check, we can't
		// delete the current directory.
		SetInternalErrorMessage( );
		result = false;
	}

	return result;
}

bool CStdDiskObject::RemoveDirectories( const tstring& directory )
/* ============================================================
	Function :		CStdDiskObject::RemoveDirectories
	Description :	Will remove the directory "directory", even
					if not empty. Will remove subdirectories.
	Access :		Public

	Return :		bool				-	"true" if OK. 
											"GetErrorMessage" 
											will get an error 
											tstring if "false"
	Parameters :	tstring directory	-	root directory to 
											remove.

	Usage :			Call to remove a directory tree.

  ============================================================*/
{
	ClearError( );

	bool result = true;

	tstring indir( directory );
	if( indir.length() )
	{
		result = QualifyPath( indir );
		if( result )
		{
			vector< tstring > directories;

			// Get all directories
			result = EnumAllDirectories( indir, directories );
			if( result )
			{
				// Loop and remove
				int max = directories.size( );
				for( int t = max - 1; t >= 0 ; t-- )
				{
					result = RemoveDirectory( directories[ t ] );
					if( !result )
						t = -1;
				}
				
				if( result )
					result = RemoveDirectory( indir );
			}
		}
	}
	else
	{
		// Small sanity check, we can't
		// delete the current directory.
		SetInternalErrorMessage( );
		result = false;
	}

	return result;
}

bool CStdDiskObject::EmptyDirectories( const tstring& directory )
/* ============================================================
	Function :		CStdDiskObject::EmptyDirectories
	Description :	Will delete all files in "directory". Will 
					also empty subdirectories.
	Access :		Public
					
	Return :		bool				-	"true" if OK. 
											"GetErrorMessage" 
											will get an error 
											tstring if "false"
	Parameters :	tstring directory	-	the root directory 
											to empty.

	Usage :			Call to empty a directory tree.

  ============================================================*/
{
	bool result = false;
	ClearError( );

	tstring indir( directory );

	if( QualifyPath( indir ) )
	{
		vector< tstring > directories;

		// Get all directories
		result = EnumAllDirectories( indir, directories );
		if( result )
		{
			int max = directories.size( );
			// Loop and empty
			for( int t = max - 1 ; t >= 0 ; t-- )
			{
				result = EmptyDirectory( directories[ t ] );
				if( !result )
					t = -1;
			}

			if( result )
				result = EmptyDirectory( indir );
		}
	}

	return result;
}

bool CStdDiskObject::CopyDirectory( const tstring& sourceDirectory,const tstring& destDirectory )
/* ============================================================
	Function :		CStdDiskObject::CopyDirectory
	Description :	Copies all the files from "sourceDirectory" 
					to "destDirectory". Existing files will be
					overwritten. "destDirectory" will be created
					if necessary. Subdirectories will not be
					copied.
	Access :		Public
					
	Return :		bool					-	"true" if OK. 
												"GetErrorMessage" 
												will get an error 
												tstring if "false"
	Parameters :	tstring sourceDirectory	-	copy from.
					tstring destDirectory	-	copy to.

	Usage :			Call to copy a directory to another 
					directory.

  ============================================================*/
{
	ClearError( );
	tstring source( sourceDirectory );
	tstring dest( destDirectory );
	if( QualifyPath( source ) )
	{
		if( QualifyPath( dest ) )
		{

			TriggerStdDiskObject( dest.c_str() );

			// We don't care if this fails - CopyFiles will 
			// return an error in that case.
			CreateDirectory( dest );

			return CopyFiles( source, dest );
		}
	}

	return false;

}

bool CStdDiskObject::CopyDirectories( const tstring& sourceDirectory,const tstring& destDirectory )
/* ============================================================
	Function :		CStdDiskObject::CopyDirectories
	Description :	Copies all the files and subdirectories 
					from "sourceDirectory" to "destDirectory", 
					keeping the directory structure. Existing 
					files will be overwritten. "destDirectory" 
					and subdirectories will be created if 
					necessary.
	Access :		Public

					
	Return :		bool					-	"true" if OK. 
												"GetErrorMessage" 
												will get an 
												error tstring 
												if "false"
	Parameters :	tstring sourceDirectory	-	copy from.
					tstring destDirectory	-	copy to.

	Usage :			Call to copy a directory tree to a new 
					directory tree.

  ============================================================*/
{
	bool result = false;
	ClearError( );

	tstring source( sourceDirectory );
	tstring dest( destDirectory );
	if( QualifyPath( source ) )
	{
		if( QualifyPath( dest ) )
		{

			// Enumerate all directories and files below sourceDirectory
			vector< tstring > directories;
			directories.push_back( source );

			result = EnumAllDirectories( source, directories );
			if( result )
			{
				// Create and copy directories
				int max = directories.size( );
				for( int t = 0 ; t < max ; t++ )
				{
					// Create names and copy
					tstring from = directories[ t ];
					tstring part = from.substr( source.length(), from.length( ) - source.length( ) );
					tstring to = dest + part;

					result = CopyFiles( from, to );
					if( !result )
						t = max;
				}
			}
		}
	}

	return result;
}

////////////////////////////////////////
// File-oriented operations
//

bool CStdDiskObject::FileExists( const tstring& file )
/* ============================================================
	Function :		CStdDiskObject::FileExists
	Description :	Returns "true" if the file file exists
	Access :		Public
					
	Return :		bool			-	"true" if found
	Parameters :	tstring file	-	file to check

	Usage :			Call to check for file existence.

  ============================================================*/
{
	ClearError();

	bool result = true;

	if( file.length( ) )
	{
		tstring infile( file );
		result = QualifyFile( infile );
		if( result )
		{
			int filehandle = _open( infile.c_str(), _O_RDONLY );
			if( filehandle == _IO_FAILURE )
				result = false;
			else
				close( filehandle );
		}
	}
	else
	{
		SetInternalErrorMessage();
		result = false;
	}

	return result;
}

bool CStdDiskObject::FileInformation( const tstring& file, struct _stat &file_info )
/* ============================================================
	Function :		CStdDiskObject::FileInformation
	Description :	Returns "true" if the file file exists
					At this time file_info is filled out
	Access :		Public

	Return :		bool				-	"true" if found
	Parameters :	tstring file		-	file to check
					_stat &file_info	-	info filled out

	Usage :			Call to check for file existence and if there
					return data about that file.

  ============================================================*/
{
	ClearError( );

	bool result = true;

	if( file.length( ) )
	{
		tstring infile( file );
		result = QualifyFile( infile );
		if( result )
		{
			int filehandle = _open( infile.c_str(), _O_RDONLY );
			if( filehandle == _IO_FAILURE )
			{
				result = false;
			}
			else
			{
				struct _stat file_info_local;
				if( _fstat( filehandle, &file_info_local ) == _IO_SUCCESS )
				{
					memcpy(&file_info, &file_info_local, sizeof(file_info_local));
				}
				else
				{
					result = false;
					SetInternalErrorMessage( );
				}
				_close( filehandle );
			}
		}
	}
	else
	{
		SetInternalErrorMessage( );
		result = false;
	}

	return result;

}

bool CStdDiskObject::CreateFile( const tstring& file )
/* ============================================================
	Function :		CStdDiskObject::CreateFile
	Description :	Creates the file "file", as well as the 
					directories necessary
	Access :		Public

	Return :		bool			-	"true" if OK
	Parameters :	tstring file	-	file to create

	Usage :			Call to create a file.

  ============================================================*/
{
	ClearError( );

	bool result = true;

	if( file.length( ) )
	{

		tstring infile( file );
		result = QualifyFile( infile );
		if( result )
		{
			// Split into directory and file
			tstring directory = GetDirectoryName( infile );
			tstring filename = GetFileName( infile );

			result = CreateDirectory( directory );
			if( result )
			{
				TriggerStdDiskObject( file.c_str() );
				int filehandle = _tcreat( infile.c_str(), _S_IREAD | _S_IWRITE );

				if( filehandle == _IO_FAILURE )
					result = false;
				else
					close( filehandle );
			}
		}
	}
	else
	{
		SetInternalErrorMessage( );
		result = false;
	}

	return result;
}

bool CStdDiskObject::MoveFile( const tstring& sourceFile, const tstring& destFile )
/* ============================================================
	Function :		CStdDiskObject::MoveFile
	Description :	Move the file "sourceFile" to "destFile". 
					Creates the destination directory if 
					necessary

	Access :		Public

	Return :		bool						- "true" if renamed OK
	Parameters :	const tstring& sourceFile	- File to move
					const tstring& desFile		- New name/location

	Usage :			Call to move a file.

  ============================================================*/
{

	ClearError( );

	bool result = false;

	if( sourceFile.length() && destFile.length() )
	{
		tstring insourcefile( sourceFile );
		if( QualifyFile( insourcefile ) )
		{
			tstring indestdir = GetDirectoryName( destFile );
			tstring indestfilename = GetFileName( destFile );

			if( CopyFile( insourcefile, indestdir, indestfilename ) )
			{
				result = RemoveFile( insourcefile );
			}
		}
	}

	return result;

}

bool CStdDiskObject::RenameFile( const tstring& sourceFile, const tstring& destFile )
/* ============================================================
	Function :		CStdDiskObject::RenameFile
	Description :	Renames the file "sourceFile" to "destFile". 
					"destFile" is assumed to be a filename rather 
					than a fully qualified path.

	Access :		Public

	Return :		bool						- "true" if renamed OK
	Parameters :	const tstring& sourceFile	- file to rename
					const tstring& desFile		- New name

	Usage :			Call to rename a file.

   ============================================================*/
{

	ClearError( );

	bool result = false;

	if( sourceFile.length() && destFile.length() )
	{
		tstring insourcefile( sourceFile );
		if( QualifyFile( insourcefile ) )
		{
			tstring insourcedir = GetDirectoryName( insourcefile );
			if( CopyFile( sourceFile, insourcedir, destFile ) )
			{
				result = RemoveFile( sourceFile );
			}
		}
	}

	return result;

}

////////////////////////////////////////
// Helpers
//

bool CStdDiskObject::EnumFilesInDirectory( const tstring& sourceDirectory, vector< tstring >& files, int mode )
/* ============================================================
	Function :		CStdDiskObject::EnumFilesInDirectory
	Description :	Enumerates all files in directory 
					"sourceDirectory", and adds them to the 
					"vector< tstring >" "files". Will not add 
					subdirectories
	Access :		Public

	Return :		bool					-	"true" if OK. 
												"GetErrorMessage" 
												will contain errors
	Parameters :	tstring sourceDirectory	-	The directory to 
												enumerate
					vector< tstring >& files		-	The resulting files
					
	Usage :			Call to get a list of files in a directory.

  ============================================================*/
{
	return EnumFilesInDirectoryWithFilter( _T( "*.*" ), sourceDirectory, files, mode );
}

bool CStdDiskObject::EnumFilesInDirectoryWithFilter( const tstring& strFilter,const tstring& sourceDirectory, vector< tstring >& files, int mode )
/* ============================================================
	Function :		CStdDiskObject::EnumFilesInDirectoryWithFilter
	Description :	Enumerates all files matching "strFilter" in 
					the directory "sourceDirectory", and adds 
					them to the "vector< tstring >" "files". Will not 
					add subdirectories.
	Access :		Public
					
	Return :		bool					-	"true" if OK. 
												"GetErrorMessage" 
												will contain errors
	Parameters :	tstring strFilter		-	the file name filter
					tstring sourceDirectory	-	source directory
					vector< tstring >& files		-	output array
					
	Usage :			Call to get a filtered list of files from 
					a directory.

  ============================================================*/
{

	ClearError( );
	tstring source( sourceDirectory );
	bool result = QualifyPath( source );
	if( result )
	{
		if( DirectoryExists( source ) )
		{
			result = true;
			tstring sourceFiles( source + strFilter );
			struct _tfinddata_t ff;

			long findhandle = _tfindfirst( sourceFiles.c_str(), &ff );
			if( findhandle != _IO_FAILURE )
			{
				int res = 0;
				while( res == _IO_SUCCESS )
				{
					// We only want files
					if( !( ff.attrib & _A_SUBDIR ) )
					{
						bool    added = false;
						tstring file = ff.name;

						if( mode == EF_FULLY_QUALIFIED )
							file = source + file;

						// Return the array alphabetically ordered
						vector < tstring >::iterator iter;
						for( iter = files.begin() ; iter != files.end() ; iter++ )
						{
							if( *iter > file )
							{
								files.insert( iter, file );
								added = true;
								break;
							}
						}

						if ( !added )
						{
							files.push_back( file );
						}

						TriggerStdDiskObject( file.c_str() );
					}

					res = _tfindnext( findhandle, &ff );
				}

				_findclose( findhandle );
			}
		}
		else
		{

			SetSystemErrorMessage( errno, sourceDirectory );
			result = false;
		}
	}

	return result;

}
 
bool CStdDiskObject::EnumAllDirectories( const tstring& sourceDirectory, vector< tstring >& directories, int mode )
/* ============================================================
	Function :		CStdDiskObject::EnumAllDirectories
	Description :	Enumerates all directories recursively in 
					directory "sourceDirectory", and adds them 
					to the "vector< tstring >" "directories". Each entry 
					will be a fully qualified name
	Access :		Public

	Return :		bool							-	"true" if OK. 
														"GetErrorMessage" 
														will contain errors
	Parameters :	tstring sourceDirectory			-	start directory
					vector< tstring >& directories	-	"vector< tstring >" to be 
														filled with the 
														directory names.
					int mode						-   "EF_RELATIVE_NAMES" if only 
														the names relative to "sourceDirectory" 
														should be returned

	Usage :			Call to get the directory names in a 
					directory tree.

   ============================================================*/
{

	tstring source( sourceDirectory );
	bool result = QualifyPath( source );
	if( result )
	{
		result = EnumAllDirs( sourceDirectory, directories );

		if( mode == EF_RELATIVE_NAMES )
		{
			vector< tstring > temp;
			int max = directories.size();
			for( int t = 0 ; t < max ; t++ )
			{
				tstring newname = directories[ t ];
				newname = newname.substr( source.length(), newname.length() );
				newname = newname.substr( 0, newname.length() - 1 ); // strip final slash
				temp.push_back( newname );
			}
			directories.clear();
			max = temp.size();
			for( t = 0 ; t < max ; t++ )
				directories.push_back( temp[ t ] );
		}
	}

	return result;

}
 
bool CStdDiskObject::EnumAllDirs( const tstring& sourceDirectory, vector< tstring >& directories )
/* ============================================================
	Function :		CStdDiskObject::EnumAllDirs
	Description :	Enumerates all directories recursively in 
					directory "sourceDirectory", and adds them 
					to the "vector< tstring >" "directories". Each entry 
					will be a fully qualified name
	Access :		Private

	Return :		bool							-	"true" if OK. 
														"GetErrorMessage" 
														will contain errors
	Parameters :	tstring sourceDirectory			-	start directory
					vector< tstring >& directories	-	"vector< tstring >" to be 
														filled with the 
														directory names.

	Usage :			Call to get the directory names in a 
					directory tree. Used internally.

   ============================================================*/
{
	ClearError( );

	tstring source( sourceDirectory );
	bool result = QualifyPath( source );
	if( result )
	{
		tstring sourceFiles( source + _T( "*.*" ) );
		
		struct _tfinddata_t ff;

		long findhandle = _tfindfirst( sourceFiles.c_str(), &ff );
		if( findhandle != _IO_FAILURE )
		{

			int res = 0;
			while( res == _IO_SUCCESS )
			{
				// We only want directories
				if( ( ff.attrib & _A_SUBDIR ) && _tcscmp( ff.name, _T( "." ) ) && _tcscmp( ff.name, _T( ".." ) ) )
				{
					tstring directory( source + ff.name );
					directories.push_back( directory + _TCHAR( '\\' ) );

					TriggerStdDiskObject( ff.name );
					result = EnumAllDirs( directory, directories );
					if( !result )
						res = _IO_FAILURE;
				}

				if( res != _IO_FAILURE )
					res = _tfindnext( findhandle, &ff );
			}

			_findclose( findhandle );
		}
		else
		{

			SetSystemErrorMessage( errno, sourceFiles );
			result = false;
		}
	}

	return result;

}
 
bool CStdDiskObject::EnumDirectories( const tstring& sourceDirectory, vector< tstring >& directories, int mode )
/* ============================================================
	Function :		CStdDiskObject::EnumDirectories
	Description :	Enumerates all directories in directory 
					"sourceDirectory", and adds them to the 
					"vector< tstring >" "directories". Each entry will
					be a fully qualified name
	Access :		Public

	Return :		bool							-	"true" if OK. 
														"GetErrorMessage" 
														will contain errors
	Parameters :	tstring sourceDirectory			-	start directory
					vector< tstring >& directories	-	"vector< tstring >" to be 
														filled with the 
														directory names.
					int mode						-   "EF_RELATIVE_NAMES" if only 
														the names relative to "sourceDirectory" 
														should be returned


	Usage :			Call to get a list of directories in a 
					directory tree.

   ============================================================*/
{
	ClearError( );

	tstring source( sourceDirectory );
	bool result = QualifyPath( source );
	if( result )
	{
		tstring sourceFiles( source + _T( "*.*" ) );
		struct _tfinddata_t ff;

		long findhandle = _tfindfirst( sourceFiles.c_str(), &ff );
		if( findhandle != _IO_FAILURE )
		{

			int res = 0;
			while( res == _IO_SUCCESS )
			{
				// We only want directories
				if( ( ff.attrib & _A_SUBDIR ) && _tcscmp( ff.name, _T( "." ) ) && _tcscmp( ff.name, _T( ".." ) ) )
				{
					if( mode == EF_RELATIVE_NAMES )
						directories.push_back( ff.name );
					else
					{
						tstring file( source + ff.name );
						directories.push_back( file );
					}

					TriggerStdDiskObject( ff.name );
				}

				res = _tfindnext( findhandle, &ff );
			}

			_findclose( findhandle );
		}
		else
		{

			SetSystemErrorMessage( errno, sourceFiles );
			result = false;
		}
	}

	return result;
}
 
bool CStdDiskObject::EnumAllFiles( const tstring& sourceDirectory, vector< tstring >& files, int mode )
/* ============================================================
	Function :		CStdDiskObject::EnumAllFiles
	Description :	Returns, recursively, the fully qualified 
					names of all files found under 
					"sourceDirectory".
	Access :		Public

	Return :		bool						-	"true" if OK.
	Parameters :	tstring sourceDirectory		-	Root dir	
					vector< tstring >& files	-	output list 
													of all files 
													found
					int mode					-	If "EF_RELATIVE_NAMES" returns 
													names relative to "sourceDirectory"

	Usage :			Call to get the names of all files in a 
					directory tree.

   ============================================================*/
{
	return EnumAllFilesWithFilter( _T( "*.*" ), sourceDirectory, files, mode );
}

bool CStdDiskObject::EnumAllFilesWithFilter( const tstring& filter,const tstring& sourceDirectory, vector< tstring >& files, int mode )
/* ============================================================
	Function :		CStdDiskObject::EnumAllFiles
	Description :	Returns, recursively, the fully qualified 
					names of all files matching the filter
					found under "sourceDirectory".
	Access :		Public

	Return :		bool						-	"true" if OK.
	Parameters :	tstring filter				-	Filter for files
													to find
					tstring sourceDirectory		-	Root dir	
					vector< tstring >& files	-	output list 
													of all files 
													found
					int mode					-	If "EF_RELATIVE_NAMES" returns 
													names relative to "sourceDirectory"
	Usage :			Call to get a filtered list of all files in 
					a directory tree.

   ============================================================*/
{
	tstring indir = sourceDirectory;
	bool result = false;

	if( QualifyPath( indir ) )
	{

		result = EnumAllFilesWF( filter, indir, files );

		if( mode == EF_RELATIVE_NAMES )
		{
			vector< tstring > temp;
			int max = files.size();
			for( int t = 0 ; t < max ; t++ )
			{
				tstring newname = files[ t ];
				newname = newname.substr( indir.length(), newname.length() );
				temp.push_back( newname );
			}
			files.clear();
			max = temp.size();
			for( t = 0 ; t < max ; t++ )
				files.push_back( temp[ t ] );
		}
	}

	return result;

}

bool CStdDiskObject::EnumAllFilesWF( const tstring& filter,const tstring& sourceDirectory, vector< tstring >& files )
/* ============================================================
	Function :		CStdDiskObject::EnumAllFilesWF
	Description :	Returns, recursively, the fully qualified 
					names of all files matching the filter
					found under "sourceDirectory".
	Access :		Private

	Return :		bool						-	"true" if OK.
	Parameters :	tstring filter				-	Filter for files
													to find
					tstring sourceDirectory		-	Root dir	
					vector< tstring >& files	-	output list 
													of all files 
													found
					int mode					-   "EF_RELATIVE_NAMES" will return 
													names relative to "sourceDirectory"

	Usage :			Call to get a filtered list of all files in 
					a directory tree. Called internally.

   ============================================================*/
{
	vector< tstring > dirs;
	vector< tstring > dirs2;
	vector< tstring > dirfiles;
	bool result = false;
	tstring indir = sourceDirectory;
	if( QualifyPath( indir ) )
	{

		if (EnumFilesInDirectoryWithFilter( filter, indir, dirfiles, EF_FULLY_QUALIFIED )) 
		{
			int max0 = dirfiles.size( );
			for( int i = 0 ; i < max0 ; i++ )
			{
				tstring file = dirfiles[ i ];
				TriggerStdDiskObject( file.c_str() );
				files.push_back( file );
			}

			// StringArray resetting 
			dirfiles.clear();
			result = true;
		}

		if( EnumAllDirectories( sourceDirectory, dirs2, EF_FULLY_QUALIFIED ) )
		{
			int max1 = dirs2.size( );
			for( int t = 0 ; t < max1 ; t++ )
			{
				tstring dir = dirs2[ t ];
				QualifyPath( dir );
				if( EnumFilesInDirectoryWithFilter( filter, dir, dirfiles, EF_FULLY_QUALIFIED ) ) 
				{
					int max2 = dirfiles.size( );
					for( int i = 0 ; i < max2 ; i++ )
					{
						tstring file = dirfiles[ i ];
						TriggerStdDiskObject( file.c_str() );
						files.push_back( file );
					}
					// StringArray resetting
					dirfiles.clear();
				}

			}

			result = true;
		}
		else
			result = false;
	}

	return result;

}

////////////////////////////////////////
// Error handling

tstring CStdDiskObject::GetErrorMessage( )
/* ============================================================
	Function :		CStdDiskObject::GetErrorMessage
	Description :	Will return the current error message for 
					the "CStdDiskObject".
	Access :		Public
					
	Return :		tstring	-	current error message.
	Parameters :	none

	Usage :			Call to get the error message.

   ============================================================*/
{
	return m_errorMessage;
}

// Private methods

void CStdDiskObject::ClearError( )
/* ============================================================
	Function :		CStdDiskObject::ClearError
	Description :	Clears the internal error message tstring
	Access :		Private
					
	Return :		void
	Parameters :	none

	Usage :			Call to clear the internal error message.

   ============================================================*/
{
	m_errorMessage = _T( "" );
}

void CStdDiskObject::SetSystemErrorMessage( int error, const tstring& elements )
/* ============================================================
	Function :		CStdDiskObject::SetSystemErrorMessage
	Description :	Sets the error message tstring with the 
					system error message as gotten from 
					"errno". "elements" will be appended.
	Access :		Private

	Return :		void
	Parameters :	int error			-	error number from 
											"errno"
					tstring elements	-	files or directories 
											to add to the message
					
	Usage :			Call to set the error message.

   ============================================================*/
{

	_TCHAR errTemplate[] = { _T( "errno: %d" ) };
	_TCHAR buf[ 128 ];

	wsprintf( buf, errTemplate, error );
	m_errorMessage = buf;

	if( elements.length( ) )
		m_errorMessage += _TCHAR( ' ' ) + elements;

	TriggerStdDiskObject ( m_errorMessage.c_str() );

}

void CStdDiskObject::SetInternalErrorMessage( )
/* ============================================================
	Function :		CStdDiskObject::SetInternalErrorMessage
	Description :	Sets the error message to the internal error
					message
	Access :		Private

	Return :		void
	Parameters :	none

	Usage :			Call to set the error message.

   ============================================================*/
{
	m_errorMessage = _T( "CStdDiskObject internal error - check call parameters" );
	TriggerStdDiskObject ( m_errorMessage.c_str() );
}

////////////////////////////////////////
// Helpers

bool CStdDiskObject::QualifyPath( tstring& str )
/* ============================================================
	Function :		CStdDiskObject::QualifyPath
	Description :	Creates a fully qualified path from "str". 
	Access :		Private
					
	Return :		bool				"true" if success
	Parameters :	tstring& str	-	the path to qualify

	Usage :			Call to create a fully qualified path.

   ============================================================*/
{

	bool result = true;

	basic_string <char>::size_type found = str.find( _TCHAR( '/' ) );
	while( found != tstring::npos )
	{
		str[ found ] = _TCHAR( '\\' );
		found = str.find( _TCHAR( '/' ) );
	}

	// Append a trailing backslash to directory name
	if( str.length( ) )
		if( str[ str.length( ) - 1 ] != _TCHAR( '\\' ) && str[ str.length( ) - 1 ] != _TCHAR( '/' ) )
			str += _TCHAR( '\\' );

	if( str.length( ) < 2 || str.substr( 0, 2 ) != "\\\\" )
		result = QualifyFile( str );

	return result;

}

bool CStdDiskObject::QualifyFile( tstring& str )
/* ============================================================
	Function :		CStdDiskObject::QualifyFile
	Description :	Creates a fully qualified path from "str". 
	Access :		Private

	Return :		bool				"true" if success
	Parameters :	tstring& str	-	the filename to qualify

	Usage :			Call to create a fully qualified filename.

   ============================================================*/
{

	bool result = true;

	basic_string <char>::size_type found = str.find( _TCHAR( '/' ) );
	while( found != tstring::npos )
	{
		str[ found ] = _TCHAR( '\\' );
		found = str.find( _TCHAR( '/' ) );
	}

	_TCHAR drive[ _MAX_DRIVE ];
	_TCHAR dir[ _MAX_DIR ];
	_TCHAR fname[ _MAX_FNAME ];
	_TCHAR ext[ _MAX_EXT ];

	_tsplitpath( str.c_str(), drive, dir, fname, ext );

	if( !_tcsclen( drive ) )
	{
		tstring test( dir );
		// The name is not fully qualified.
		// We assume it to be relative the 
		// current directory

		TCHAR currentDirectory[ _MAX_PATH ];
		_tgetcwd( currentDirectory, _MAX_PATH );
		if( test.length() > 2 && test.substr( 0, 2 ) == _T( "\\\\" ) )
		{
			// We have an UNC-part, and will not need to qualify further.
		}
		if( test.length( ) > 2 && test.substr( 0, 3 ) == _T( "..\\" ) )
		{

			TCHAR upDirectory[ _MAX_PATH ];
			while( test.length( ) > 2 && test.substr( 0, 3 ) == _T( "..\\" ) )
			{
				_tchdir( _T( ".." ) );
				test = test.substr( 3, test.length( ) - 3 );
			}

			_tgetcwd( upDirectory, _MAX_PATH );

			str = upDirectory;
			str += _TCHAR( '\\' );
			str += fname;
			str += ext;
			_tchdir( currentDirectory );
		}
		else if ( test.length( ) && test[ 0 ] == _TCHAR( '\\' ) )
		{
			// Start from root
			_tsplitpath( currentDirectory, drive, dir, fname, ext );
			str = drive + str;
		}
		else
		{
			str = _TCHAR( '\\' ) + str;
			str = currentDirectory + str;
		}
	}

	if( !TestPart( dir ) || 
		!TestPart( fname ) ||
		!TestPart( ext ) )
	{
		SetInternalErrorMessage();
		result = false;
	}

	return result;

}

bool CStdDiskObject::TestPart( const tstring& str )
/* ============================================================
	Function :		CStdDiskObject::TestPart
	Description :	Checks the string for invalid characters
	Access :		Private
					
	Return :		bool		-	"true" if ok
	Parameters :	tstring str	-	The string to test

	Usage :			Call to test if a string contains illegal
					filename characters. Used internally.

   ============================================================*/
{
	bool result = true;
	if( str.find( _TCHAR( ':') ) != tstring::npos )
		result = false;
	if( str.find( _TCHAR( '*') ) != tstring::npos )
		result = false;
	if( str.find( _TCHAR( '?') ) != tstring::npos )
		result = false;

	return result;
}

tstring CStdDiskObject::GetFileName( const tstring& str )
/* ============================================================
	Function :		CStdDiskObject::GetFileName
	Description :	Returns the file name part of "str".
	Access :		Private
					
	Return :		tstring		-	The file part
	Parameters :	tstring str	-	The complete name to get

	Usage :			Call to get the filename from a fully 
					qualified path.

   ============================================================*/
{
	tstring instr( str );
	tstring file;
	if( QualifyFile( instr ) )
	{
		basic_string <char>::size_type found = instr.rfind( _TCHAR( '\\' ) );
		if( found != tstring::npos )
			file = instr.substr( found + 1, instr.length( ) - ( found + 1 ) );
	}

	return file;
}

tstring CStdDiskObject::GetDirectoryName( const tstring& str )
/* ============================================================
	Function :		CStdDiskObject::GetDirectoryName
	Description :	Returns the directory name part of "str"
	Access :		Private
					
	Return :		tstring		-	The directory part
	Parameters :	tstring str	-	The complete name to check	

	Usage :			Call to get the directory from a fully 
					qualified path.

   ============================================================*/
{
	tstring instr( str );
	tstring directory;
	if( QualifyFile( instr ) )
	{
		basic_string <char>::size_type found = instr.rfind( _TCHAR( '\\' ) );
		if( found != tstring::npos )
			directory = instr.substr( 0, found );
	}

	return directory;
}

bool copyfile( LPCTSTR from, LPCSTR to )
/*
	Helper function to copy a file without
	using the Win-API
*/
{

	bool result = false;
	int file1 = _topen( from, _O_RDONLY | _O_BINARY );
	if( file1 != _IO_FAILURE )
	{
		int file2 = _topen( to, _O_WRONLY | _O_CREAT, _S_IREAD | _S_IWRITE | _O_BINARY );
		if( file2 != _IO_FAILURE )
		{
			int block = 32 * 1024;
			int read;
			char* buff = new char[ block ];
			if( buff )
			{
				read = _read( file1, buff, block );
				while ( read )
				{
					_write( file2, buff, read );
					read = _read( file1, buff, block );
				}

				result = true;
				delete [] buff;
			}

			_close( file2 );

		}
		_close( file1 );
	}

	return result;
}

bool CStdDiskObject::RenameDirectory( const tstring& oldDirectory, const tstring& newName )
/* ============================================================
	Function :		CDiskObject::RenameDirectory
	Description :	Renames the directory "oldDirectory" to 
					"newName",
	Access :		Public
					
	Return :		bool					-	"true" if renamed ok.
	Parameters :	tstring oldDirectory	-	The directory to rename
					tstring newName			-	The new name

	Usage :			Call to rename a directory.

   ============================================================*/
{
	bool result = false;

	basic_string <char>::size_type findDir = newName.find( _TCHAR( '\\' ) );
	if( findDir == tstring::npos )
	{
		tstring fullName( oldDirectory );

		if( QualifyPath( fullName ) )
		{
			tstring fullNewName( fullName );
			fullNewName = fullNewName.substr( 0, fullNewName.length() - 1 );
			findDir = fullNewName.rfind( _TCHAR( '\\' ) );
			if( findDir != tstring::npos )
			{
				fullNewName = fullNewName.substr( 0, findDir + 1 );
			}
			fullNewName += newName;
			if( _trename( fullName.c_str(), fullNewName.c_str() ) )
			{

				SetSystemErrorMessage( errno, 
										fullName + 
										_T( " -> " ) + 
										fullNewName );
			}
			else
				result = true;
		}
	}

	return result;
}

bool CStdDiskObject::MoveDirectory( const tstring& oldDirectory, const tstring& newDirectory )
/* ============================================================
	Function :		CDiskObject::MoveDirectory
	Description :	Moves the directory "oldDirectory" to 
					"newDirectory",
	Access :		Public
					
	Return :		BOOL					-	"true" if moved ok.
	Parameters :	tstring oldDirectory	-	The directory to move
					tstring newDirectory	-	The new location

	Usage :			Call to move a directory. Can't move a 
					directory inside itself.

   ============================================================*/
{

	ClearError();

	bool result = false;
	int len = oldDirectory.length() > newDirectory.length() ? newDirectory.length() : oldDirectory.length();
	if( oldDirectory.length() && newDirectory.length() && _tcsncicmp(newDirectory.c_str(), oldDirectory.c_str(), len ) != 0 )
	{
		if( CopyDirectories( oldDirectory, newDirectory ) )
		{
			result = RemoveDirectories( oldDirectory );
		}
	}
	else
	{
		SetInternalErrorMessage();
	}

	return result;

}

